﻿using System;
using System.Linq;
using FluentValidation;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Services;
using LINQPad.OpenAccess.Settings;
using Telerik.OpenAccess.Metadata;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    /// BackendSettings - ViewModel
    /// </summary>
    public class BackendSettingsViewModel : DriverSubSettingsViewModelBase<BackendSettingsViewModel>, IPersistableSettingsViewModel<DriverSettings>
    {
        #region Private

        #region - Vars

        private readonly IBackendService _backendService;
        private readonly Backend[] _supportedBackends;
        private string[] _providerNames;
        private Backend? _selectedBackend, _defaultBackend;
        private string _selectedProviderName, _defaultProviderName;
        private bool _useDefaultBackend;


        #endregion

        #region - Functions

        /// <summary>
        /// Loads the providers for the backend.
        /// </summary>
        private void LoadProvidersForBackend( )
        {
            var backend = this.UseDefaultBackend ? this._defaultBackend : this._selectedBackend;

            this.ProviderNames = backend.HasValue
                                    ? this._backendService
                                            .GetProviderMappingsForBackend( backend.Value )
                                            .Select( c => c.ProviderName )
                                            .ToArray( )
                                    : null;
        }
        /// <summary>
        /// Syncs the selected provider name to the specified owner.
        /// </summary>
        /// <param name="owner">The owner.</param>
        private void SyncSelectedProviderNameToOwner( DriverSettingsViewModel owner )
        {
            if ( owner != null && owner.ConnectionSettingsHost != null )
            {
                owner.ConnectionSettingsHost
                    .ProviderName = this.UseDefaultBackend ? this._defaultProviderName : this._selectedProviderName;
            }
        }
        /// <summary>
        /// Updates the name of the selected provider.
        /// </summary>
        /// <param name="preferredProviderName">Name of the preferred provider.</param>
        private void UpdateSelectedProviderName( string preferredProviderName = null )
        {
            var selectedProviderName = preferredProviderName;
            var hasProviderNames = this.HasProviderNames;

            if ( String.IsNullOrWhiteSpace( selectedProviderName ) )
                selectedProviderName = this._defaultProviderName;
            if ( !String.IsNullOrWhiteSpace( selectedProviderName ) )
                selectedProviderName = !hasProviderNames ? null : this._providerNames.FirstOrDefault( e => selectedProviderName.Equals( e ) );
            if ( selectedProviderName == null )
                selectedProviderName = !hasProviderNames ? null : this._providerNames[0];

            this.SelectedProviderName = selectedProviderName;
        }

        #endregion

        #endregion

        #region Protected

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether the default constructor is used for the initialization of the context.
        /// </summary>
        /// <value>
        /// <c>true</c> if the default constructor is used; otherwise, <c>false</c>.
        /// </value>
        protected bool DefaultConstructorIsUsed
        {
            get
            {
                return this.Owner != null && this.Owner.BasicSettings != null && this.Owner.BasicSettings.UseDefaultConstructor;
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BackendSettingsViewModel" /> class.
        /// </summary>
        /// <param name="backendService">The backend service.</param>
        /// <param name="validator">The validator.</param>
        public BackendSettingsViewModel( IBackendService backendService, IValidator<BackendSettingsViewModel> validator )
            : base( validator )
        {
            Guard.ArgumentNotNull( backendService, "backendService" );

            this._backendService = backendService;
            this._supportedBackends = this._backendService.GetSupportedBackends( );
        }

        #endregion

        #region - Functions

        /// <summary>
        /// Sets the default backend.
        /// </summary>
        /// <param name="backend">The backend.</param>
        /// <param name="providerName">Name of the provider.</param>
        public void SetDefaultBackend( Backend? backend, string providerName )
        {
            if ( this._defaultBackend != backend || this._defaultProviderName != providerName )
            {
                this._defaultBackend = backend;
                this._defaultProviderName = providerName;

                this.OnPropertyChanged( ( ) => this.DefaultProviderName );
                this.OnPropertyChanged( ( ) => this.DefaultBackend );
                this.OnPropertyChanged( ( ) => this.UseDefaultBackend );
                this.OnPropertyChanged( ( ) => this.CanUseDefaultBackend );
            }
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether the default backend can be used.
        /// </summary>
        /// <value>
        /// <c>true</c> if the default backend can be used; otherwise, <c>false</c>.
        /// </value>
        public bool CanUseDefaultBackend
        {
            get
            {
                return !String.IsNullOrWhiteSpace( this._defaultProviderName ) || this.DefaultConstructorIsUsed;
            }
        }
        /// <summary>
        /// Gets the default backend.
        /// </summary>
        /// <value>
        /// The default backend.
        /// </value>
        public Backend? DefaultBackend
        {
            get
            {
                return this._defaultBackend;
            }
        }
        /// <summary>
        /// Gets the default name of the provider.
        /// </summary>
        /// <value>
        /// The default name of the provider.
        /// </value>
        public string DefaultProviderName
        {
            get
            {
                return this._defaultProviderName;
            }
        }
        /// <summary>
        /// Gets a value indicating whether this instance has provider names.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has provider names; otherwise, <c>false</c>.
        /// </value>
        public bool HasProviderNames
        {
            get
            {
                return this._providerNames != null && this._providerNames.Length > 0;
            }
        }
        /// <summary>
        /// Gets a value indicating whether this instance has supported backends.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has supported backends; otherwise, <c>false</c>.
        /// </value>
        public bool HasSupportedBackends
        {
            get
            {
                return this._supportedBackends != null && this._supportedBackends.Length > 0;
            }
        }
        /// <summary>
        /// Gets or sets the provider names.
        /// </summary>
        /// <value>
        /// The provider names.
        /// </value>
        public string[] ProviderNames
        {
            get
            {
                return this._providerNames;
            }
            set
            {
                if ( this._providerNames != value )
                {
                    this._providerNames = value ?? new string[0];

                    this.OnPropertyChanged( ( ) => this.ProviderNames );
                    this.OnPropertyChanged( ( ) => this.HasProviderNames );

                    this.UpdateSelectedProviderName( preferredProviderName: this._selectedProviderName );
                }
            }
        }
        /// <summary>
        /// Gets or sets the selected backend.
        /// </summary>
        /// <value>
        /// The selected backend.
        /// </value>
        public Backend? SelectedBackend
        {
            get
            {
                return this._selectedBackend;
            }
            set
            {
                if ( this._selectedBackend != value )
                {
                    this._selectedBackend = value;

                    this.OnPropertyChanged( ( ) => this.SelectedBackend );
                    this.LoadProvidersForBackend( );
                    this.Invalidate( );
                }
            }
        }
        /// <summary>
        /// Gets or sets the name of the selected provider.
        /// </summary>
        /// <value>
        /// The name of the selected provider.
        /// </value>
        public string SelectedProviderName
        {
            get
            {
                return this._selectedProviderName;
            }
            set
            {
                if ( this._selectedProviderName != value )
                {
                    this._selectedProviderName = value;
                    this.OnPropertyChanged( ( ) => this.SelectedProviderName );
                    this.SyncSelectedProviderNameToOwner( this.Owner );
                    this.Invalidate( );
                }
            }
        }
        /// <summary>
        /// Gets the supported backends.
        /// </summary>
        /// <value>
        /// The supported backends.
        /// </value>
        public Backend[] SupportedBackends
        {
            get
            {
                return this._supportedBackends;
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether the default backend should be used.
        /// </summary>
        /// <value>
        /// <c>true</c> if the default backend should be used; otherwise, <c>false</c>.
        /// </value>
        public bool UseDefaultBackend
        {
            get
            {
                if ( !this.CanUseDefaultBackend )
                    return false;
                return this._useDefaultBackend || this.DefaultConstructorIsUsed;
            }
            set
            {
                if ( this._useDefaultBackend != value )
                {
                    this._useDefaultBackend = value;
                    this.OnPropertyChanged( ( ) => this.UseDefaultBackend );

                    this.SyncSelectedProviderNameToOwner( this.Owner );
                    this.LoadProvidersForBackend( );

                    this.Invalidate( );
                }
            }
        }

        #endregion

        #endregion

        #region DriverSubSettingsViewModelBase<BackendSettingsViewModel> Members

        #region - Functions

        /// <summary>
        /// Called when the assigned owner has changed.
        /// </summary>
        /// <param name="newOwner">The new owner.</param>
        /// <param name="oldOwner">The old owner.</param>
        protected override void OnOwnerChanged( DriverSettingsViewModel newOwner, DriverSettingsViewModel oldOwner )
        {
            this.SyncSelectedProviderNameToOwner( newOwner );
        }

        #endregion

        #endregion

        #region IPersistableSettingsViewModel<DriverSettings> Members

        /// <summary>
        /// Tries to load the view model's settings from the specified sources.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="source">The source.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryLoadSettingsFrom( IConnectionInfo connectionInfo, DriverSettings source )
        {
            if ( connectionInfo == null || source == null )
                return false;

            var backendSettings = source.Backend;
            var databaseInfo = connectionInfo.DatabaseInfo;

            this.SelectedBackend = this.UseDefaultBackend || backendSettings == null ? null : backendSettings.Backend;
            this.UpdateSelectedProviderName( preferredProviderName: databaseInfo == null ? null : databaseInfo.Provider );
            // we don't update the default provider and default backend here:
            //   - we don't want to rely on previously stored settings, since the assembly may have changed meanwhile.
            //     => the owner extracts each time the default backend settings from assembly and sets them via the
            //        SetDefaultBackend method.
            this.UseDefaultBackend = backendSettings != null && backendSettings.IsDefault;

            this.SyncSelectedProviderNameToOwner( this.Owner );

            return true;
        }
        /// <summary>
        /// Writes the view model's settings to the specified targets.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="target">The target.</param>
        public void WriteSettingsTo( IConnectionInfo connectionInfo, DriverSettings target )
        {
            Guard.ArgumentNotNull( connectionInfo, "connectionInfo" );
            Guard.ArgumentNotNull( target, "target" );
            Guard.ArgumentNotNull( connectionInfo.DatabaseInfo, "connectionInfo.DatabaseInfo" );

            if ( target.Backend == null )
                target.Backend = new BackendSettings( );

            var useDefault = this.UseDefaultBackend;
            // in case of using the default backend, we only store the provider name & backend for convenience reasons,
            // up on load they're re-extracted from the context's assembly.
            target.Backend.Backend = useDefault ? this._defaultBackend : this._selectedBackend;
            connectionInfo.DatabaseInfo.Provider = useDefault ? this._defaultProviderName : this._selectedProviderName;
            target.Backend.IsDefault = useDefault;
        }

        #endregion
    }
}
